<html xmlns="https://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="zh-CN" />
<link href="../style/css/manual-zip.css" rel="stylesheet" media="all" type="text/css" title="Main stylesheet" />
<link href="../style/css/manual-zip-100pc.css" rel="alternate stylesheet" media="all" type="text/css" title="No Sidebar - Default font size" />
<link href="../style/css/manual-print.css" rel="stylesheet" media="print" type="text/css" />
<title>高级URL重写指南 － Apache 2.2 中文手册 [金步国]</title>
<script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?d286c55b63a3c54a1e43d10d4c203e75"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script>
</head>
<body id="manual-page"><div id="page-header">
<p class="menu"><a href="../mod/index.html">模块索引</a> | <a href="../mod/directives.html">指令索引</a> | <a href="../faq/index.html">常见问题</a> | <a href="../glossary.html">词汇表</a> | <a href="../sitemap.html">站点导航</a></p><p class="apache">Apache HTTP Server 版本2.2</p><img alt="" src="../images/feather.gif" /></div>
<div class="up"><a href="index.html"><img title="&lt;-" alt="&lt;-" src="../images/left.gif" /></a></div>
<div id="path"><a href="https://www.apache.org/">Apache</a> &gt; <a href="https://httpd.apache.org/">HTTP Server</a> &gt; <a href="https://httpd.apache.org/docs/">文档</a> &gt; <a href="../index.html">版本2.2</a> &gt; <a href="index.html">URL重写</a></div>

<div id="translation-info">　　 <a href="../translator_announcement.html#thanks">致谢</a> | 本篇译者：<a href="../../../index.html">金步国</a>(<a href="../../../index.html">作品集</a>) | 本页最后更新：2006年10月20日</div>
<div id="page-content"><div id="preamble"><h1>高级URL重写指南</h1>



    <p>本文是<code class="module"><a href="../mod/mod_rewrite.html">mod_rewrite</a></code><a href="../mod/mod_rewrite.html">参考文档</a>的补充材料。阐述在实际应用中如何解决网管所面临的基于URL的典型问题，并详细描述了如何配置URL重写规则集以解决这些问题。</p>

    <div class="warning">注意：根据你的服务器配置，有可能必须对这里的例子作些小修改，比如，在额外启用<code class="module"><a href="../mod/mod_alias.html">mod_alias</a></code>和<code class="module"><a href="../mod/mod_userdir.html">mod_userdir</a></code>的情况下要增加<code>[PT]</code>标志，或者为了适应目录级(<code>.htaccess</code>)的配置而将针对服务器级的规则集进行重写。对一个特定的规则集应该先透彻理解然后再考虑应用，这样才能避免出现问题。</div>

  </div>
	<div class="top"><a href="rewrite_guide_advanced.html#page-header"><img alt="top" src="../images/up.gif" /></a></div>
<div class="section">
<h2><a name="cluster" id="cluster">集群网站的同类URL规划</a></h2>



      <dl>
        <dt>描述：</dt>

        <dd>我们希望在一个Intranet集群网站中，对所有WWW服务器建立一致的URL规划，也就是说，所有的URL(针对每个服务器进行本地配置，因此是独立于各个服务器的)实际上都是<em>独立于</em>各个服务器的！我们需要的是一个具有独立于各个服务器的一致性的WWW域名空间，即URL不需要包含物理目标服务器，而由集群本身来自动定位物理目标主机。</dd>

        <dt>解决方案：</dt>

        <dd>首先，目标服务器的信息来自于(分布式的)包含有用户、组以及实体的外部映射表，其格式形如下：

<div class="example"><pre>
user1  server_of_user1
user2  server_of_user2
:      :
</pre></div>
这些信息被存入<code>map.xxx-to-host</code>文件。其次，如果URL在一个服务器上无效，我们还需要引导所有的服务器将
<div class="example"><pre>
/u/user/anypath
/g/group/anypath
/e/entity/anypath
</pre></div>
重定向到
<div class="example"><pre>
http://physical-host/u/user/anypath
http://physical-host/g/group/anypath
http://physical-host/e/entity/anypath
</pre></div>
以下规则集依靠映射文件来完成这个操作(如果一个用户在映射表中没有对应的项，则使用server0做为默认服务器)：
<div class="example"><pre>
RewriteEngine on

RewriteMap      user-to-host   txt:/path/to/map.user-to-host
RewriteMap     group-to-host   txt:/path/to/map.group-to-host
RewriteMap    entity-to-host   txt:/path/to/map.entity-to-host

RewriteRule   ^/u/<strong>([^/]+)</strong>/?(.*)   http://<strong>${user-to-host:$1|server0}</strong>/u/$1/$2
RewriteRule   ^/g/<strong>([^/]+)</strong>/?(.*)  http://<strong>${group-to-host:$1|server0}</strong>/g/$1/$2
RewriteRule   ^/e/<strong>([^/]+)</strong>/?(.*) http://<strong>${entity-to-host:$1|server0}</strong>/e/$1/$2

RewriteRule   ^/([uge])/([^/]+)/?$          /$1/$2/.www/
RewriteRule   ^/([uge])/([^/]+)/([^.]+.+)   /$1/$2/.www/$3\
</pre></div>
        </dd>
      </dl>

    </div><div class="top"><a href="rewrite_guide_advanced.html#page-header"><img alt="top" src="../images/up.gif" /></a></div>
<div class="section">
<h2><a name="structuredhomedirs" id="structuredhomedirs">结构化的用户主目录</a></h2>



      <dl>
        <dt>描述：</dt>

        <dd>一些拥有几千个用户的网站通常都使用结构化的用户主目录规划，即每个用户主目录位于一个带有特定前缀(比如其用户名的第一个字符)的子目录下：<code>/~foo/anypath</code>代表<code>/home/<strong>f</strong>/foo/.www/anypath</code>，而<code>/~bar/anypath</code>代表<code>/home/<strong>b</strong>/bar/.www/anypath</code></dd>

        <dt>解决方案：</dt>

        <dd>可以使用下列规则集来扩展'<code>~</code>'以达到上述目的。
<div class="example"><pre>
RewriteEngine on
RewriteRule   ^/~(<strong>([a-z])</strong>[a-z0-9]+)(.*)  /home/<strong>$2</strong>/$1/.www$3
</pre></div>
        </dd>
      </dl>

    </div><div class="top"><a href="rewrite_guide_advanced.html#page-header"><img alt="top" src="../images/up.gif" /></a></div>
<div class="section">
<h2><a name="filereorg" id="filereorg">文件系统的重组</a></h2>



      <dl>
        <dt>描述：</dt>

        <dd>这是一个不加雕琢的例子：一个大量使用目录级规则集以实现平滑的观感，并且从来不用调整数据结构的杀手级的应用。背景：从1992年开始，<strong><em>net.sw</em></strong>存放了我收集的免费Unix软件包。它是我的爱好也是我的工作，因为在学习计算机科学的同时，业余时间还做了多年的系统和网络管理员。每周我都需要整理软件，因而建立了一个层次很深的目录结构来存放各种软件包：
<div class="example"><pre>
drwxrwxr-x   2 netsw  users    512 Aug  3 18:39 Audio/
drwxrwxr-x   2 netsw  users    512 Jul  9 14:37 Benchmark/
drwxrwxr-x  12 netsw  users    512 Jul  9 00:34 Crypto/
drwxrwxr-x   5 netsw  users    512 Jul  9 00:41 Database/
drwxrwxr-x   4 netsw  users    512 Jul 30 19:25 Dicts/
drwxrwxr-x  10 netsw  users    512 Jul  9 01:54 Graphic/
drwxrwxr-x   5 netsw  users    512 Jul  9 01:58 Hackers/
drwxrwxr-x   8 netsw  users    512 Jul  9 03:19 InfoSys/
drwxrwxr-x   3 netsw  users    512 Jul  9 03:21 Math/
drwxrwxr-x   3 netsw  users    512 Jul  9 03:24 Misc/
drwxrwxr-x   9 netsw  users    512 Aug  1 16:33 Network/
drwxrwxr-x   2 netsw  users    512 Jul  9 05:53 Office/
drwxrwxr-x   7 netsw  users    512 Jul  9 09:24 SoftEng/
drwxrwxr-x   7 netsw  users    512 Jul  9 12:17 System/
drwxrwxr-x  12 netsw  users    512 Aug  3 20:15 Typesetting/
drwxrwxr-x  10 netsw  users    512 Jul  9 14:08 X11/
</pre></div>
1996年7月，我决定通过一个漂亮的Web接口公开我的收藏。"漂亮"是指提供一个直接浏览整个目录结构的接口，同时不对这个结构做任何改变，甚至也不在结构顶部放置CGI脚本。为什么呢？因为这个结构还要能够被FTP访问，而且我不希望其中有任何Web或者CGI成分。</dd>

        <dt>解决方案：</dt>

        <dd>这个方案分为两个部分：第一个部分是一组CGI脚本，用于实时建立所有目录层次的页面。我把它们放在<code>/e/netsw/.www/</code>中：
<div class="example"><pre>
-rw-r--r--   1 netsw  users    1318 Aug  1 18:10 .wwwacl
drwxr-xr-x  18 netsw  users     512 Aug  5 15:51 DATA/
-rw-rw-rw-   1 netsw  users  372982 Aug  5 16:35 LOGFILE
-rw-r--r--   1 netsw  users     659 Aug  4 09:27 TODO
-rw-r--r--   1 netsw  users    5697 Aug  1 18:01 netsw-about.html
-rwxr-xr-x   1 netsw  users     579 Aug  2 10:33 netsw-access.pl
-rwxr-xr-x   1 netsw  users    1532 Aug  1 17:35 netsw-changes.cgi
-rwxr-xr-x   1 netsw  users    2866 Aug  5 14:49 netsw-home.cgi
drwxr-xr-x   2 netsw  users     512 Jul  8 23:47 netsw-img/
-rwxr-xr-x   1 netsw  users   24050 Aug  5 15:49 netsw-lsdir.cgi
-rwxr-xr-x   1 netsw  users    1589 Aug  3 18:43 netsw-search.cgi
-rwxr-xr-x   1 netsw  users    1885 Aug  1 17:41 netsw-tree.cgi
-rw-r--r--   1 netsw  users     234 Jul 30 16:35 netsw-unlimit.lst
</pre></div>
其中的<code>DATA/</code>子目录包含了上述目录结构，即实际的<strong><em>net.sw</em></strong>原始内容，由<code>rdist</code>在需要的时候自动更新。第二个部分的问题是：如何将这两个结构连接为一个观感平滑的URL树？我希望在为各种URL运行对应的CGI脚本的时候，用户感觉不到<code>DATA/</code>目录的存在。方案如下：首先，我把下列配置放在针对每个目录的配置文件里，将公布的URL"<code>/net.sw/</code>"重写为内部路径"<code>/e/netsw</code>"：
<div class="example"><pre>
RewriteRule  ^net.sw$       net.sw/        [R]
RewriteRule  ^net.sw/(.*)$  e/netsw/$1
</pre></div>
第一条规则是针对遗漏后缀斜杠的请求的！第二条规则才是真正实现功能的。接着，就是放在目录级配置文件<code>/e/netsw/.www/.wwwacl</code>中的杀手级的配置了：
<div class="example"><pre>
Options       ExecCGI FollowSymLinks Includes MultiViews

RewriteEngine on

# 通过"/net.sw/"前缀到达
RewriteBase   /net.sw/

# 首先将根目录重写到cgi处理脚本
RewriteRule   ^$                       netsw-home.cgi     [L]
RewriteRule   ^index\.html$            netsw-home.cgi     [L]

# 当浏览器请求perdir页面时剥去子目录
RewriteRule   ^.+/(netsw-[^/]+/.+)$    $1                 [L]

# 现在打断对本地文件的重写
RewriteRule   ^netsw-home\.cgi.*       -                  [L]
RewriteRule   ^netsw-changes\.cgi.*    -                  [L]
RewriteRule   ^netsw-search\.cgi.*     -                  [L]
RewriteRule   ^netsw-tree\.cgi$        -                  [L]
RewriteRule   ^netsw-about\.html$      -                  [L]
RewriteRule   ^netsw-img/.*$           -                  [L]

# 任何别的东西都是一个由另一个cgi脚本处理的子目录
RewriteRule   !^netsw-lsdir\.cgi.*     -                  [C]
RewriteRule   (.*)                     netsw-lsdir.cgi/$1
</pre></div>

          <p>阅读提示：</p>

          <ol>
            <li>注意前半部分中的<code>L</code>(最后)标志和非替换部分('<code>-</code>')</li>
            <li>注意后半部分中的<code>!</code>(非)符号和<code>C</code>(链)标志</li>
            <li>注意最后一条规则是全匹配模式</li>
          </ol>
        </dd>
      </dl>

    </div><div class="top"><a href="rewrite_guide_advanced.html#page-header"><img alt="top" src="../images/up.gif" /></a></div>
<div class="section">
<h2><a name="redirect404" id="redirect404">将失败的URL重定向到其他web服务器</a></h2>



      <dl>
        <dt>描述：</dt>

        <dd>一个常见的问题是如何将对web服务器A的失败请求重定向到服务器B。一般，可以使用借助<code class="directive"><a href="../mod/core.html#errordocument">ErrorDocument</a></code>的CGI脚本来解决，此外，还有使用<code class="module"><a href="../mod/mod_rewrite.html">mod_rewrite</a></code>的方案。但是须注意，这种方法的执行效率不如使用<code class="directive"><a href="../mod/core.html#errordocument">ErrorDocument</a></code>的CGI脚本！</dd>

        <dt>解决方案：</dt>

        <dd>第一种方案，有最好的性能而灵活性欠佳，出错概率小所以安全：

<div class="example"><pre>
RewriteEngine on
RewriteCond   /your/docroot/%{REQUEST_FILENAME} <strong>!-f</strong>
RewriteRule   ^(.+)                             http://<strong>webserverB</strong>.dom/$1
</pre></div>
但是问题在于，它只对位于<code class="directive"><a href="../mod/core.html#documentroot">DocumentRoot</a></code>中的页面有效。虽然可以增加更多的条件(比如同时还处理用户主目录，等等)，但是还有一个更好的方法：
<div class="example"><pre>
RewriteEngine on
RewriteCond   %{REQUEST_URI} <strong>!-U</strong>
RewriteRule   ^(.+)          http://<strong>webserverB</strong>.dom/$1
</pre></div>
这种方法使用了<code class="module"><a href="../mod/mod_rewrite.html">mod_rewrite</a></code>提供的"前瞻"(look-ahead)的功能，是一种对所有URL类型都有效而且安全的方法，但是对web服务器的性能有不利影响。如果web服务器有一个强大的CPU，那就用这个方法。而在慢速机器上，可以用第一种方法，或者用性能更好的CGI脚本。</dd>
      </dl>

    </div><div class="top"><a href="rewrite_guide_advanced.html#page-header"><img alt="top" src="../images/up.gif" /></a></div>
<div class="section">
<h2>文档访问的多路复用</h2>

      <dl>
        <dt>描述：</dt>

        <dd>你知道<a href="http://www.perl.com/CPAN">http://www.perl.com/CPAN</a>的CPAN(综合Perl存档网络)吗？它实现了一个重定向以提供全世界的CPAN镜像中离访问者最近的FTP站点，也可以称之为FTP访问多路复用服务。CPAN是通过CGI脚本实现的，那么用<code class="module"><a href="../mod/mod_rewrite.html">mod_rewrite</a></code>如何实现呢？</dd>

        <dt>解决方案：</dt>

        <dd>首先，<code class="module"><a href="../mod/mod_rewrite.html">mod_rewrite</a></code>从3.0.0版本开始可以重写"<code>ftp:</code>"类型。其次，可以用<code class="directive"><a href="../mod/mod_rewrite.html#rewritemap">RewriteMap</a></code>取得对客户端顶级域名的最短路径。利用链式规则集，并用顶级域名作为查找多路复用映射表的键，可以这样做：
<div class="example"><pre>
RewriteEngine on
RewriteMap    multiplex                txt:/path/to/map.cxan
RewriteRule   ^/CxAN/(.*)              %{REMOTE_HOST}::$1                 [C]
RewriteRule   ^.+\.<strong>([a-zA-Z]+)</strong>::(.*)$  ${multiplex:<strong>$1</strong>|ftp.default.dom}$2  [R,L]
</pre></div>

<div class="example"><pre>
##
##  map.cxan -- CxAN多路映射表
##

de        ftp://ftp.cxan.de/CxAN/
uk        ftp://ftp.cxan.uk/CxAN/
com       ftp://ftp.cxan.com/CxAN/
 :
##EOF##
</pre></div>
        </dd>
      </dl>

    </div><div class="top"><a href="rewrite_guide_advanced.html#page-header"><img alt="top" src="../images/up.gif" /></a></div>
<div class="section">
<h2><a name="content" id="content">内容处理</a></h2>



   <h3>依赖于浏览器的内容</h3>

      <dl>
        <dt>描述：</dt>

        <dd>有时候有必须提供依赖于浏览器的最佳内容(至少对重要的顶级页面)，即对最新的Netscape提供最大化的版本，对Lynx提供最小化的版本，而对其他的浏览器则提供一个一般的版本。</dd>

        <dt>解决方案：</dt>

        <dd>对此，内容协商无能为力，因为浏览器不提供那种形式的类型，所以只能在"User-Agent"头上想办法。以下规则集可以完成这个操作：如果"User-Agent"以"Mozilla/3"开头，则将<code>foo.html</code>重写为<code>foo.NS.html</code>，并终止重写操作；如果是"Lynx"或者版本号为1和2的"Mozilla"，则重写为<code>foo.20.html</code>；而对其他所有浏览器则是<code>foo.32.html</code>。
<div class="example"><pre>
RewriteCond %{HTTP_USER_AGENT}  ^<strong>Mozilla/3</strong>.*
RewriteRule ^foo\.html$         foo.<strong>NS</strong>.html          [<strong>L</strong>]

RewriteCond %{HTTP_USER_AGENT}  ^<strong>Lynx/</strong>.*         [OR]
RewriteCond %{HTTP_USER_AGENT}  ^<strong>Mozilla/[12]</strong>.*
RewriteRule ^foo\.html$         foo.<strong>20</strong>.html          [<strong>L</strong>]

RewriteRule ^foo\.html$         foo.<strong>32</strong>.html          [<strong>L</strong>]
</pre></div>
        </dd>
      </dl>



    <h3>动态镜像</h3>

      <dl>
        <dt>描述：</dt>

        <dd>假定，需要在我们的域名空间里加入其他远程主机的页面。对FTP服务器，可以用<code>mirror</code>程序在本地机器上维持一个对远程数据的最新的拷贝；对HTTP服务器，可以使用<code>webcopy</code>程序。但这两种技术都有一个主要的缺点：本地拷贝必须通过这个程序来更新。所以，比较好的方法是不采用静态镜像，而采用动态镜像，即在有数据请求时自动更新(远程主机上更新的数据)。</dd>

        <dt>解决方案：</dt>

        <dd>为达到此目的，可以使用<dfn>代理吞吐</dfn>(Proxy Throughput)功能(<code>[P]</code>标志)，将远程页面甚至整个远程网络区域映射到我们的域名空间：
<div class="example"><pre>
RewriteEngine  on
RewriteBase    /~quux/
RewriteRule    ^<strong>hotsheet/</strong>(.*)$  <strong>http://www.tstimpreso.com/hotsheet/</strong>$1  [<strong>P</strong>]
</pre></div>

<div class="example"><pre>
RewriteEngine  on
RewriteBase    /~quux/
RewriteRule    ^<strong>usa-news\.html</strong>$   <strong>http://www.quux-corp.com/news/index.html</strong>  [<strong>P</strong>]
</pre></div>
        </dd>
      </dl>



    <h3>反向动态镜像</h3>



      <dl>
        <dt>描述：</dt>

        <dd>...</dd>

        <dt>解决方案：</dt>

        <dd>
<div class="example"><pre>
RewriteEngine on
RewriteCond   /mirror/of/remotesite/$1           -U
RewriteRule   ^http://www\.remotesite\.com/(.*)$ /mirror/of/remotesite/$1
</pre></div>
        </dd>
      </dl>



    <h3>通过Intranet取得丢失的数据</h3>



      <dl>
        <dt>描述：</dt>

        <dd>这在受防火墙保护的内部网(<code>www2.quux-corp.dom</code>)上保存和维护实际数据，而在Internet上虚拟地运行web服务器(<code>www.quux-corp.dom</code>)。方法是外部服务器在空闲时间从内部服务器取得被请求的数据。</dd>

        <dt>解决方案：</dt>

        <dd>首先，必须确保防火墙对内部服务器的保护，并只允许此外部服务器获取数据。对包过滤(packet-filtering)防火墙，可以制定如下防火墙规则：
<div class="example"><pre>
<strong>ALLOW</strong> Host www.quux-corp.dom Port &gt;1024 --&gt; Host www2.quux-corp.dom Port <strong>80</strong>
<strong>DENY</strong>  Host *                 Port *     --&gt; Host www2.quux-corp.dom Port <strong>80</strong>
</pre></div>
请按你的实际情况，对上例稍作调整。接着，建立通过代理后台获取丢失数据的重写规则：
<div class="example"><pre>
RewriteRule ^/~([^/]+)/?(.*)          /home/$1/.www/$2
RewriteCond %{REQUEST_FILENAME}       <strong>!-f</strong>
RewriteCond %{REQUEST_FILENAME}       <strong>!-d</strong>
RewriteRule ^/home/([^/]+)/.www/?(.*) http://<strong>www2</strong>.quux-corp.dom/~$1/pub/$2 [<strong>P</strong>]
</pre></div>
        </dd>
      </dl>



    <h3>负载均衡</h3>



      <dl>
        <dt>描述：</dt>

        <dd>如何将<code>www.foo.com</code>的负载均衡到<code>www[0-5].foo.com</code>(一共是6个服务器)?</dd>

        <dt>解决方案：</dt>

        <dd>这个问题有许多可能的解决方案，在此，我们讨论通称为"基于DNS"的方案，和特殊的使用<code class="module"><a href="../mod/mod_rewrite.html">mod_rewrite</a></code>的方案

          <ol>
            <li>
              <strong>DNS循环</strong>

              <p>最简单的方法是用<code>BIND</code>的DNS循环特性，只要按惯例设置<code>www[0-9].foo.com</code>的DNS的A(地址)记录，如：</p>

<div class="example"><pre>
www0   IN  A       1.2.3.1
www1   IN  A       1.2.3.2
www2   IN  A       1.2.3.3
www3   IN  A       1.2.3.4
www4   IN  A       1.2.3.5
www5   IN  A       1.2.3.6
</pre></div>
然后，增加以下各项：
<div class="example"><pre>
www    IN  CNAME   www0.foo.com.
       IN  CNAME   www1.foo.com.
       IN  CNAME   www2.foo.com.
       IN  CNAME   www3.foo.com.
       IN  CNAME   www4.foo.com.
       IN  CNAME   www5.foo.com.
       IN  CNAME   www6.foo.com.
</pre></div>
注意，上述看起来似乎是错误的，但事实上，它的确是<code>BIND</code>中一个特意的特性，而且也可以这样用。在解析<code>www.foo.com</code>时，<code>BIND</code>将给出<code>www0-www6</code>的结果(虽然每次在次序上会有轻微的置换/循环)，客户端的请求可以被分散到各个服务器。但这并不是一个优秀的负载均衡方案，因为DNS解析信息可以被网络中其他域名服务器缓冲，一旦<code>www.foo.com</code>被解析为<code>wwwN.foo.com</code>，该请求的所有后继请求都将被送往<code>wwwN.foo.com</code>。但是最终结果是正确的，因为请求的总量的确被分散到各个服务器了。
            </li>

            <li>
              <strong>DNS负载均衡</strong>

              <p>一种成熟的基于DNS的负载均衡方法是使用<a href="http://www.stanford.edu/~schemers/docs/lbnamed/lbnamed.html"><code>lbnamed</code></a>程序，它是一个Perl程序，并带有若干辅助工具，实现了真正的基于DNS的负载均衡。</p>
            </li>

            <li>
              <strong>代理吞吐循环(Proxy Throughput Round-Robin)</strong>

              <p>这是一个使用<code class="module"><a href="../mod/mod_rewrite.html">mod_rewrite</a></code>以及代理吞吐特性的方法。首先，在DNS记录中将<code>www0.foo.com</code>固定为<code>www.foo.com</code>，如下：</p>

<div class="example"><pre>
www    IN  CNAME   www0.foo.com.
</pre></div>

              <p>其次，将<code>www0.foo.com</code>转换为一个专职代理服务器，即由这个机器把所有到来的URL通过内部代理分散到另外5个服务器(<code>www1-www5</code>)。为此，必须建立一个规则集，对所有URL调用一个负载均衡脚本<code>lb.pl</code> 。</p>

<div class="example"><pre>
RewriteEngine on
RewriteMap    lb      prg:/path/to/lb.pl
RewriteRule   ^/(.+)$ ${lb:$1}           [P,L]
</pre></div>
下面是<code>lb.pl</code>的内容：
<div class="example"><pre>
#!/path/to/perl
## lb.pl -- 负载平衡脚本

$| = 1;

$name   = "www";     # the hostname base
$first  = 1;         # the first server (not 0 here, because 0 is myself)
$last   = 5;         # the last server in the round-robin
$domain = "foo.dom"; # the domainname

$cnt = 0;
while (&lt;STDIN&gt;) {
    $cnt = (($cnt+1) % ($last+1-$first));
    $server = sprintf("%s%d.%s", $name, $cnt+$first, $domain);
    print "http://$server/$_";
}

##EOF##
</pre></div>

              <div class="note">最后的说明：这样有用吗？似乎<code>www0.foo.com</code>也会超载呀？答案是：没错，它的确会超载，但是它超载的仅仅是简单的代理吞吐请求！所有诸如SSI、CGI、ePerl等的处理完全是由其他机器完成的，这个才是重点。</div>
            </li>

            <li>
              <strong>硬件/TCP循环</strong>

              <p>还有一个硬件解决方案。Cisco有一个叫LocalDirector的东西，实现了TCP/IP层的负载均衡，事实上，它是一个位于网站集群前端的电路级网关。如果你有足够资金而且的确需要高性能的解决方案，那么可以用这个。</p>
            </li>
          </ol>
        </dd>
      </dl>



    <h3>新的MIME类型，新的服务</h3>

      <dl>
        <dt>描述：</dt>

        <dd>网上有许多很巧妙的CGI程序，但是用法晦涩，许多网管弃之不用。即使是Apache的MEME类型的动作处理器，也仅仅在CGI程序不需要在其输入中包含<code>PATH_INFO</code>和<code>QUERY_STRINGS</code>时才很好用。首先，配置一种新的后缀为<code>.scgi</code>的(安全CGI)文件类型，其处理器是很常见的<code>cgiwrap</code>程序。问题是：如果使用同类URL规划(见上述)，而用户宿主目录中的一个文件的URL是<code>/u/user/foo/bar.scgi</code>，可是<code>cgiwrap</code>要求的URL的格式是<code>/~user/foo/bar.scgi/</code>，以下重写规则解决了这个问题：
<div class="example"><pre>
RewriteRule ^/[uge]/<strong>([^/]+)</strong>/\.www/(.+)\.scgi(.*) ...
... /internal/cgi/user/cgiwrap/~<strong>$1</strong>/$2.scgi$3  [NS,<strong>T=application/x-http-cgi</strong>]
</pre></div>
另外，假设还需要使用其他程序：<code>wwwlog</code>(显示<code>access.log</code>中的一个URL子树)和<code>wwwidx</code>(对一个URL子树运行Glimpse)，则必须为两个程序提供URL区域作为其操作对象。比如，对<code>/u/user/foo/</code>执行<code>swwidx</code>程序的超链是这样的：
<div class="example"><pre>
/internal/cgi/user/swwidx?i=/u/user/foo/
</pre></div>
其缺点是，必须<strong>同时</strong>硬编码超链中的区域<strong>和</strong>CGI的路径，如果重组了这个区域，就需要花费大量时间来修改各个超链。</dd>

        <dt>解决方案：</dt>

        <dd>用一个特殊的新的URL格式，自动拼装CGI参数：
<div class="example"><pre>
RewriteRule   ^/([uge])/([^/]+)(/?.*)/\*  /internal/cgi/user/wwwidx?i=/$1/$2$3/
RewriteRule   ^/([uge])/([^/]+)(/?.*):log /internal/cgi/user/wwwlog?f=/$1/$2$3
</pre></div>
现在，这个搜索<code>/u/user/foo/</code>的超链简化成了：
<div class="example"><pre>
HREF="*"
</pre></div>
它会在内部被自动转换为
<div class="example"><pre>
/internal/cgi/user/wwwidx?i=/u/user/foo/
</pre></div>
这样就可以为使用"<code>:log</code>"的超链拼装出调用CGI程序的参数。
        </dd>
      </dl>



    <h3>传输过程中的内容协商</h3>

      <dl>
        <dt>描述：</dt>

        <dd>这是一个很深奥的功能：动态地生成但静态地发送(从文件系统中读出，然后直接发出去)，但是如果它丢失了，则由服务器动态生成。这样，可以静态地提供CGI生成的页面，除非有人(或者是一个计划任务)删除了这些静态页面，而且其内容可以得到更新。</dd>

        <dt>解决方案：</dt>

        <dd>以下规则集实现了这个功能：
<div class="example"><pre>
RewriteCond %{REQUEST_FILENAME}   <strong>!-s</strong>
RewriteRule ^page\.<strong>html</strong>$          page.<strong>cgi</strong>   [T=application/x-httpd-cgi,L]
</pre></div>
这样，如果<code>page.html</code>不存在或者文件大小为null ，则对<code>page.html</code>的请求会导致<code>page.cgi</code>的运行。其中奥妙在于<code>page.cgi</code>是一个将输出写入到<code>page.html</code>的(同时也写入<code>STDOUT</code>)的CGI脚本。执行完毕，服务器则将<code>page.html</code>的内容发送出去。如果网管需要强制更新其内容，只须删除<code>page.html</code>即可(通常由一个计划任务完成)。</dd>
      </dl>



    <h3>自动更新的文档</h3>

      <dl>
        <dt>描述：</dt>

        <dd>建立一个复杂的页面，能够在用编辑器写了一个更新的版本时自动在浏览器上得到刷新，这不是很好吗？这可能吗？</dd>

        <dt>解决方案：</dt>

        <dd>这是可行的！这需要综合利用MIME多成分、web服务器的NPH和<code class="module"><a href="../mod/mod_rewrite.html">mod_rewrite</a></code>的URL操控特性。首先，建立一个新的URL特性：对在文件系统中需要刷新的所有URL加上"<code>:refresh</code>" 。
<div class="example"><pre>
RewriteRule   ^(/[uge]/[^/]+/?.*):refresh  /internal/cgi/apache/nph-refresh?f=$1
</pre></div>
现在当我们引用如下URL
<div class="example"><pre>
/u/foo/bar/page.html:refresh
</pre></div>
时，将导致在内部调用
<div class="example"><pre>
/internal/cgi/apache/nph-refresh?f=/u/foo/bar/page.html
</pre></div>
接着就是NPH-CGI脚本部分了。虽然，人们常说"将此作为一个练习留给读者"，但我还是给出答案了。
<div class="example"><pre>
#!/sw/bin/perl
##
##  nph-refresh -- 用于自动刷新页面的 NPH/CGI 脚本
##  Copyright (c) 1997 Ralf S. Engelschall, All Rights Reserved.
##
$| = 1;

# 分解 QUERY_STRING 变量
@pairs = split(/&amp;/, $ENV{'QUERY_STRING'});
foreach $pair (@pairs) {
    ($name, $value) = split(/=/, $pair);
    $name =~ tr/A-Z/a-z/;
    $name = 'QS_' . $name;
    $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
    eval "\$$name = \"$value\"";
}
$QS_s = 1 if ($QS_s eq ");
$QS_n = 3600 if ($QS_n eq ");
if ($QS_f eq ") {
    print "HTTP/1.0 200 OK\n";
    print "Content-type: text/html\n\n";
    print "&amp;lt;b&amp;gt;ERROR&amp;lt;/b&amp;gt;: No file given\n";
    exit(0);
}
if (! -f $QS_f) {
    print "HTTP/1.0 200 OK\n";
    print "Content-type: text/html\n\n";
    print "&amp;lt;b&amp;gt;ERROR&amp;lt;/b&amp;gt;: File $QS_f not found\n";
    exit(0);
}

sub print_http_headers_multipart_begin {
    print "HTTP/1.0 200 OK\n";
    $bound = "ThisRandomString12345";
    print "Content-type: multipart/x-mixed-replace;boundary=$bound\n";
    &amp;print_http_headers_multipart_next;
}

sub print_http_headers_multipart_next {
    print "\n--$bound\n";
}

sub print_http_headers_multipart_end {
    print "\n--$bound--\n";
}

sub displayhtml {
    local($buffer) = @_;
    $len = length($buffer);
    print "Content-type: text/html\n";
    print "Content-length: $len\n\n";
    print $buffer;
}

sub readfile {
    local($file) = @_;
    local(*FP, $size, $buffer, $bytes);
    ($x, $x, $x, $x, $x, $x, $x, $size) = stat($file);
    $size = sprintf("%d", $size);
    open(FP, "&amp;lt;$file");
    $bytes = sysread(FP, $buffer, $size);
    close(FP);
    return $buffer;
}

$buffer = &amp;readfile($QS_f);
&amp;print_http_headers_multipart_begin;
&amp;displayhtml($buffer);

sub mystat {
    local($file) = $_[0];
    local($time);

    ($x, $x, $x, $x, $x, $x, $x, $x, $x, $mtime) = stat($file);
    return $mtime;
}

$mtimeL = &amp;mystat($QS_f);
$mtime = $mtime;
for ($n = 0; $n &amp;lt; $QS_n; $n++) {
    while (1) {
        $mtime = &amp;mystat($QS_f);
        if ($mtime ne $mtimeL) {
            $mtimeL = $mtime;
            sleep(2);
            $buffer = &amp;readfile($QS_f);
            &amp;print_http_headers_multipart_next;
            &amp;displayhtml($buffer);
            sleep(5);
            $mtimeL = &amp;mystat($QS_f);
            last;
        }
        sleep($QS_s);
    }
}

&amp;print_http_headers_multipart_end;

exit(0);

##EOF##
</pre></div>
        </dd>
      </dl>



    <h3>海量虚拟主机</h3>



      <dl>
        <dt>描述：</dt>

        <dd>Apache的<code class="directive"><a href="../mod/core.html#virtualhost">&lt;VirtualHost&gt;</a></code>功能很强，在有几十个虚拟主机的情况下运行得仍然很好，但是如果你是ISP，需要提供成百上千个虚拟主机，那么这就不是最佳选择了。</dd>

        <dt>解决方案：</dt>

        <dd>为此，需要用<dfn>代理吞吐</dfn>(Proxy Throughput)功能(<code>[P]</code>标志)映射远程页面甚至整个远程网络区域到自己的域名空间：
<div class="example"><pre>
##
##  vhost.map
##
www.vhost1.dom:80  /path/to/docroot/vhost1
www.vhost2.dom:80  /path/to/docroot/vhost2
     :
www.vhostN.dom:80  /path/to/docroot/vhostN
</pre></div>

<div class="example"><pre>
##
##  httpd.conf
##
    :
# 在重定向时使用规范化的主机名等等
UseCanonicalName on

    :
# 在CLF-format之前添加虚拟主机
CustomLog  /path/to/access_log  "%{VHOST}e %h %l %u %t \"%r\" %&gt;s %b"
    :

# 为主服务器启用重写引擎
RewriteEngine on

#   define two maps: one for fixing the URL and one which defines
#   the available virtual hosts with their corresponding
#   DocumentRoot.
RewriteMap    lowercase    int:tolower
RewriteMap    vhost        txt:/path/to/vhost.map

#   Now do the actual virtual host mapping
#   via a huge and complicated single rule:
#
#   1. make sure we don't map for common locations
RewriteCond   %{REQUEST_URI}  !^/commonurl1/.*
RewriteCond   %{REQUEST_URI}  !^/commonurl2/.*
    :
RewriteCond   %{REQUEST_URI}  !^/commonurlN/.*
#
#   2. make sure we have a Host header, because
#      currently our approach only supports
#      virtual hosting through this header
RewriteCond   %{HTTP_HOST}  !^$
#
#   3. lowercase the hostname
RewriteCond   ${lowercase:%{HTTP_HOST}|NONE}  ^(.+)$
#
#   4. lookup this hostname in vhost.map and
#      remember it only when it is a path
#      (and not "NONE" from above)
RewriteCond   ${vhost:%1}  ^(/.*)$
#
#   5. finally we can map the URL to its docroot location
#      and remember the virtual host for logging puposes
RewriteRule   ^/(.*)$   %1/$1  [E=VHOST:${lowercase:%{HTTP_HOST}}]
    :
</pre></div>
        </dd>
      </dl>



  </div><div class="top"><a href="rewrite_guide_advanced.html#page-header"><img alt="top" src="../images/up.gif" /></a></div>
<div class="section">
<h2><a name="access" id="access">访问控制</a></h2>



    <h3>对主机的拒绝</h3>



      <dl>
        <dt>描述：</dt>

        <dd>如何禁止一批外部列表中的主机对我们服务器的访问？</dd>

        <dt>解决方案：</dt>

        <dd>

<div class="example"><pre>
RewriteEngine on
RewriteMap    hosts-deny  txt:/path/to/hosts.deny
RewriteCond   ${hosts-deny:%{REMOTE_HOST}|NOT-FOUND} !=NOT-FOUND [OR]
RewriteCond   ${hosts-deny:%{REMOTE_ADDR}|NOT-FOUND} !=NOT-FOUND
RewriteRule   ^/.*  -  [F]
</pre></div>

<div class="example"><pre>
##
##  hosts.deny
##
##  注意! 这是一个映射而不是列表(即使我们这样看待它)。
##  mod_rewrite会将它作为 键/值 对进行解析。
##  所以每一项至少要存在一个伪值："-"
##

193.102.180.41 -
bsdti1.sdm.de  -
192.76.162.40  -
</pre></div>
        </dd>
      </dl>



    <h3>对代理的拒绝</h3>



      <dl>
        <dt>描述：</dt>

        <dd>如何拒绝某个主机或者来自特定主机的用户使用Apache代理？</dd>

        <dt>解决方案：</dt>

        <dd>首先，要确保Apache配置文件中<code class="module"><a href="../mod/mod_rewrite.html">mod_rewrite</a></code>在<code class="module"><a href="../mod/mod_proxy.html">mod_proxy</a></code>的下面！使它在<code class="module"><a href="../mod/mod_proxy.html">mod_proxy</a></code><em>之前</em>被调用。然后使用如下方法拒绝某个主机：
<div class="example"><pre>
RewriteCond %{REMOTE_HOST} <strong>^badhost\.mydomain\.com$</strong>
RewriteRule !^http://[^/.]\.mydomain.com.*  - [F]
</pre></div>
使用如下方法拒绝user@host-dependent用户：
<div class="example"><pre>
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST}  <strong>^badguy@badhost\.mydomain\.com$</strong>
RewriteRule !^http://[^/.]\.mydomain.com.*  - [F]
</pre></div>
        </dd>
      </dl>



    <h3>特殊的认证</h3>



      <dl>
        <dt>描述：</dt>

        <dd>有时候，会需要一种非常特殊的认证，即对一组明确指定的用户，允许其访问，且没有(在使用<code class="module"><a href="../mod/mod_auth_basic.html">mod_auth_basic</a></code>的基本认证方法时可能会出现的)任何提示。</dd>

        <dt>解决方案：</dt>

        <dd>使用一个重写条件列表来拒绝朋友以外的所有人：
<div class="example"><pre>
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} <strong>!^friend1@client1.quux-corp\.com$</strong>
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} <strong>!^friend2</strong>@client2.quux-corp\.com$
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} <strong>!^friend3</strong>@client3.quux-corp\.com$
RewriteRule ^/~quux/only-for-friends/      -                                 [F]
</pre></div>
        </dd>
      </dl>



    <h3>基于Referer的转向器</h3>



      <dl>
        <dt>描述：</dt>

        <dd>如何配置一个基于HTTP头"Referer"的转向器以转向到任意数量的引用页(referring page)？</dd>

        <dt>解决方案：</dt>

        <dd>使用下面这个很巧妙的规则集：
<div class="example"><pre>
RewriteMap  deflector txt:/path/to/deflector.map

RewriteCond %{HTTP_REFERER} !=""
RewriteCond ${deflector:%{HTTP_REFERER}} ^-$
RewriteRule ^.* %{HTTP_REFERER} [R,L]

RewriteCond %{HTTP_REFERER} !=""
RewriteCond ${deflector:%{HTTP_REFERER}|NOT-FOUND} !=NOT-FOUND
RewriteRule ^.* ${deflector:%{HTTP_REFERER}} [R,L]
</pre></div>
并结合相应的重写映射表：
<div class="example"><pre>
##
##  deflector.map
##

http://www.badguys.com/bad/index.html    -
http://www.badguys.com/bad/index2.html   -
http://www.badguys.com/bad/index3.html   http://somewhere.com/
</pre></div>
它可以自动将请求(在映射表中指定了"<code>-</code>"值的时候)重定向回其引用页(referring page)，或者(在映射表中URL有第二个参数时)重定向到一个特定的URL。
</dd>
      </dl>



  </div></div>
<div id="footer">
<p class="apache">本文允许自由的转载、引用、再分发，但必须保留译者署名并注明出处；详见：<a href="../translator_announcement.html#announcement">版权声明</a>。</p>
<p class="menu"><a href="../mod/index.html">模块索引</a> | <a href="../mod/directives.html">指令索引</a> | <a href="../faq/index.html">常见问题</a> | <a href="../glossary.html">词汇表</a> | <a href="../sitemap.html">站点导航</a></p></div>
</body></html>
